/**
 * 地图2.0通用类，基于openLayer
 *
 * @author Mark.Yuan
 * @version 1.0.0
 */

import * as CTMapOl from "@ct/ct_map_ol"; // NOSONAR
import CryptoJS from "crypto-js";
import { parse, stringify } from "flatted";
import $ from "jquery";
import { fromLonLat } from "ol/proj";
import { isArray, isEmptyObject, isPlainObject } from "./common";

import Collection from "ol/Collection.js";
import DataTile from "ol/DataTile";
import Feature from "ol/Feature";
import OLMap from "ol/Map.js";
import Overlay from "ol/Overlay.js";
import View from "ol/View.js";
import controlAttribution from "ol/control/Attribution.js";
import controlControl from "ol/control/Control.js";
import controlFullScreen from "ol/control/FullScreen.js";
import controlMousePosition from "ol/control/MousePosition";
import controlOverviewMap from "ol/control/OverviewMap";
import controlRotate from "ol/control/Rotate";
import controlScaleLine from "ol/control/ScaleLine";
import controlZoom from "ol/control/Zoom";
import controlZoomSlider from "ol/control/ZoomSlider";
import controlZoomToExtent from "ol/control/ZoomToExtent";
import eventsEvent from "ol/events/Event";
import formatEsriJSON from "ol/format/EsriJSON.js";
import geomPolygon from "ol/geom/Polygon.js";
import interactionInteraction from "ol/interaction/Interaction.js";
import layerBase from "ol/layer/Base.js";
import layerBaseImage from "ol/layer/BaseImage.js";
import layerBaseTile from "ol/layer/BaseTile.js";
import layerBaseVector from "ol/layer/BaseVector.js";
import layerGraticule from "ol/layer/Graticule.js";
import layerGroup from "ol/layer/Group.js";
import layerHeatmap from "ol/layer/Heatmap.js";
import layerImage from "ol/layer/Image.js";
import layerLayer from "ol/layer/Layer.js";
import layerTile from "ol/layer/Tile.js";
import layerVector from "ol/layer/Vector.js";
import layerVectorImage from "ol/layer/VectorImage.js";
import layerVectorTile from "ol/layer/VectorTile.js";
import layerWebGLTile from "ol/layer/WebGLTile.js";
import sourceCluster from "ol/source/Cluster.js";
import sourceImage from "ol/source/Image.js";
import sourceOSM from "ol/source/OSM.js";
import sourceSource from "ol/source/Source.js";
import sourceTile from "ol/source/Tile.js";
import sourceVector from "ol/source/Vector.js";
import sourceWMTS from "ol/source/WMTS.js";
import sourceXYZ from "ol/source/XYZ.js";
import styleIcon from "ol/style/Icon.js";

const classCollects = {
  Map: OLMap,
  Collection,
  controlAttribution,
  controlControl,
  controlFullScreen,
  controlMousePosition,
  controlOverviewMap,
  controlRotate,
  controlScaleLine,
  controlZoom,
  controlZoomSlider,
  controlZoomToExtent,
  DataTile,
  eventsEvent,
  Feature,
  formatEsriJSON,
  interactionInteraction,
  layerBase,
  layerLayer,
  layerTile,
  layerImage,
  layerGroup,
  layerVector,
  layerHeatmap,
  layerBaseTile,
  layerBaseImage,
  layerWebGLTile,
  layerBaseVector,
  layerGraticule,
  layerVectorTile,
  layerVectorImage,
  View,
  Overlay,
  geomPolygon,
  sourceOSM,
  sourceXYZ,
  sourceTile,
  sourceSource,
  sourceImage,
  sourceWMTS,
  sourceVector,
  sourceCluster,
  styleIcon,
};

export default class ChinTowerMapOL {
  /**
   * 地图2.0 构造函数
   * @param {Object} options
   */
  constructor(options = {}) {
    if (!isPlainObject(options)) {
      options = {};
      console.warn("设置全局参数必须为Object");
    }

    const defaults = {
      el: "map", // 默认渲染的dom element
      server: "tianditu", // 地图服务名
      tile: "satellite", // 地图底图类型
      center: fromLonLat([116.39, 39.91]), // 地图中心点，默认北京天安门
      zoom: 13, // 地图层级
      minZoom: 1, // 地图最小层级
      maxZoom: 18, // 地图最大层级
      index: 0, // 当前实例化的顺序
    };

    this.config = Object.assign({}, defaults, options);
    // 密钥
    this.key = CryptoJS.enc.Utf8.parse(
      CryptoJS.enc.Base64.parse(process.env.VUE_APP_KEY).toString(
        CryptoJS.enc.Utf8
      )
    );
    // 地图实例队列
    this.queueMaps = new Map();
  }

  /**
   * 初始化地图
   */
  initMap() {
    // 实例化地图
    let { el, index = 0, ...opts } = this.config;
    opts.domId = el;

    if (index === undefined) {
      index = 0;
    }

    return new Promise(async (resolve, reject) => {
      try {
        let mapInstance = null;
        if (["satellite", "vector"].includes(opts.tile)) {
          // 二维
          mapInstance = new CTMapOl.extend.InitMap(opts);
        } else {
          // 三维
          mapInstance = await this.initCesiumMap(opts);
          console.log("三维 mapInstance::", mapInstance);
          // mapInstance = new CTMapOl.cesiumComponent.ThrdimeInitMap(opts)
        }

        // 实例的地图
        this.handleQueueMaps(`mapOLInstance-${index}`, mapInstance);

        // 加入队列，对象类实例
        // 生成唯一id
        const uuid = this.uuid();
        this.handleQueueMaps(`mapOL-${index}-${uuid}`, mapInstance?.map); // 地图实例
        resolve({
          id: `mapOL-${index}-${uuid}`,
          map: mapInstance?.map,
          mapInstance,
        });
      } catch (error) {
        console.log("error", error);
        reject(error);
      }
    });
  }

  /**
   * 初始化Cesium地图
   *
   * @param options 地图初始化参数
   * @returns Promise对象，用于异步获取Cesium地图对象
   */
  initCesiumMap(options) {
    return new Promise((resolve) => {
      CTMapOl.loadCesium(options, (map) => {
        resolve(map);
      });
    });
  }

  /**
   * 创建类的实例
   *
   * @param iSource 类实例来源，native/proxy/extend
   * @param realClassName 真实的类名
   * @param args 参数数组
   * @param opts 配置项
   * @param index 索引
   * @returns 返回创建的类实例
   */
  async createClassInstance(iSource, realClassName, args, opts, index) {
    let classInstance = null;
    if (iSource === "native") {
      const dynamicClass = realClassName?.replace("/", "");
      classInstance = !isEmptyObject(opts)
        ? new classCollects[dynamicClass](...args, opts)
        : new classCollects[dynamicClass](...args);
    } else {
      let convertClassName = "";
      if (iSource === "proxy") {
        convertClassName = this.convertClassName(realClassName, CTMapOl);
      } else if (iSource === "extend") {
        convertClassName = this.convertClassName(realClassName, CTMapOl.extend);
        if (realClassName === "InitMap") {
          this.handleQueueMaps(`mapOLInstance-${index}`, classInstance);
          classInstance = classInstance?.map;
        }
      } else {
        // other
      }
      classInstance = !isEmptyObject(opts)
        ? new convertClassName(...args, opts)
        : new convertClassName(...args);
    }
    return classInstance;
  }

  /**
   * 解析选项函数
   *
   * @param opts 选项对象
   * @param classOptions 类选项对象
   * @param index 索引
   * @returns 返回解析后的选项对象和参数数组
   */
  parseOptions(opts, classOptions, index) {
    const classArr = this.parseClassObj(classOptions, index);
    opts = Object.assign({}, opts, classArr);
    let { argKeys = "", ..._opts } = opts;
    let argKeyArr = argKeys ? argKeys.split("-") : [];
    const args = argKeys ? argKeyArr.map((key) => _opts[key]) : [];
    if (opts?.element) {
      opts.element = document.getElementById(opts.element);
    }
    return { opts, args };
  }

  /**
   * 实例化方法类
   *
   * @param {string} className 实例类名称(用-分割，标识当前实例的顺序)
   * @param {Int} index 地图实例id
   * @param {Object} opts api 参数
   * @param {*} classOptions 实例id集合
   * @returns
   */
  instantiation(iSource, className, index = 0, opts = {}, classOptions = {}) {
    return new Promise(async (resolve, reject) => {
      if (className) {
        try {
          className = this.formatClassName(className);
          const realClassName = this.getRealClassName(className);
          const { opts: parsedOpts, args } = this.parseOptions(
            opts,
            classOptions,
            index
          );
          const classInstance = await this.createClassInstance(
            iSource,
            realClassName,
            args,
            parsedOpts,
            index
          );

          // 加入队列，对象类实例
          // 生成唯一id
          const uuid = this.uuid();
          this.handleQueueMaps(`${className}-${uuid}`, classInstance);

          resolve({
            id: `${className}-${uuid}`,
          });
        } catch (error) {
          reject(error);
        }
      } else {
        reject("Missing Required Params(className)");
      }
    });
  }

  /**
   * 执行链式方法
   *
   * @param classInstance 类实例
   * @param methods 方法列表
   * @param argKeys 参数键列表
   * @param argParams 参数列表
   * @param _opts 选项
   * @param index 当前方法索引
   * @returns 执行结果
   */
  async executeChainedMethods(
    classInstance,
    methods,
    argKeys,
    argParams,
    _opts,
    index
  ) {
    let methodExecResult = classInstance;

    for (let _method of methods) {
      let otherArgParam = this.getOtherArgParams(argParams, _method, index);

      if (argKeys[_method]) {
        const args = argKeys[_method]
          .split("-")
          .map((key) => otherArgParam[key]);
        methodExecResult = methodExecResult[_method](...args);
      } else {
        methodExecResult = Object.keys(otherArgParam).length
          ? methodExecResult[_method](_opts)
          : methodExecResult[_method]();
      }
    }

    return methodExecResult;
  }

  /**
   * 执行单个方法
   *
   * @param classInstance 类实例
   * @param method 方法名
   * @param opts 选项参数
   * @param _opts 选项参数
   * @param iframe iframe元素
   * @param postMsgUtilFn 发送消息的工具函数
   * @returns 返回执行结果
   */
  async executeSingleMethod(
    classInstance,
    method,
    opts,
    _opts,
    iframe,
    postMsgUtilFn
  ) {
    const { callBack = undefined, argKeys = "", ...restOpts } = opts;
    _opts = { ..._opts, ...restOpts };

    let argCounter = classInstance[method]?.length;
    const argKeyArr = argKeys.split("-");
    const callBackFun = this.getCallBackFunction(
      callBack,
      iframe,
      postMsgUtilFn
    );

    const args = argKeyArr.map((key) => _opts[key]).concat(callBackFun);
    return await classInstance[method](...args.slice(0, argCounter));
  }

  /**
   * 获取其他参数对象
   *
   * @param argParams 参数对象
   * @param _method 方法名
   * @param index 索引
   * @returns 其他参数对象
   */
  getOtherArgParams(argParams, _method, index) {
    let otherArgParam = {};
    if (argParams[_method]) {
      const { classOption = {}, ..._otherArgParam } = argParams[_method];
      const classArr = this.parseClassObj(classOption, index);
      otherArgParam = { ..._otherArgParam, ...classArr };
    }
    return otherArgParam;
  }

  /**
   * 获取回调函数
   *
   * @param {Object} callBack 回调函数配置
   * @param {Object} iframe iframe对象
   * @param {Object} postMsgUtilFn postMessage工具函数
   * @returns {Function} 回调函数
   */
  getCallBackFunction(callBack, iframe, postMsgUtilFn) {
    if (callBack?.isNeed) {
      return (result) => {
        postMsgUtilFn &&
          iframe &&
          postMsgUtilFn.trigger(iframe, callBack.name, {
            data: {
              result: parse(stringify(result)),
              callBackName: callBack.name,
            },
          });
      };
    }
    return () => {
      // 默认初始化函数
    };
  }

  /**
   * 调用api方法
   *
   * @param {string} method 请求的方法
   * @param {int} index 地图实例序号
   * @param {string} o_id 实例化id
   * @param {object} opts 配置参数
   * @param {object} classOptions 其他类实例
   * @returns
   */
  handleApi(
    method = "",
    index = 0,
    o_id = "",
    opts = {},
    classOptions = {},
    iframe = "",
    postMsgUtilFn = ""
  ) {
    return new Promise(async (resolve, reject) => {
      if (!method || !o_id) {
        return reject("Missing Required Params(method or o_id)");
      }
      try {
        const classInstance = this.getQueueMap(o_id);
        const methods = method.split("-");

        if (typeof classInstance[methods[0]] === "undefined") {
          reject(`method "${methods[0]}" not support`);
        }

        // 解析classOptions
        const classArr = this.parseClassObj(classOptions, index);
        // 如果有argKeys，且不为空，则说明参数铺平（格式）
        let { argKeys = {}, argParams = {}, ..._opts } = opts;
        _opts = { ..._opts, ...classArr };

        // 处理 DOM 节点
        if (_opts?.element) {
          _opts.element = document.getElementById(_opts.element);
        }

        let res = "";

        // 如果有argKeys，且不为空，则说明参数铺平（格式）
        res =
          methods.length > 1
            ? await this.executeChainedMethods(
                classInstance,
                methods,
                argKeys,
                argParams,
                _opts,
                index
              )
            : await this.executeSingleMethod(
                classInstance,
                method,
                opts,
                _opts,
                iframe,
                postMsgUtilFn
              );

        res =
          isPlainObject(res) || isArray(res)
            ? parse(stringify(res))
            : { data: res };
        resolve({
          ...res,
        });
      } catch (error) {
        reject(error);
      }
    });
  }

  /**
   * 处理元素 处理element，添加/移除等操作
   *
   * @param {object} opts - 选项参数
   * @param {string} iframe - iframe 元素
   * @param {string} postMsgUtilFn - postMessage 工具函数
   * @returns {Promise} - Promise 对象
   */
  handleElement(opts = {}, iframe = "", postMsgUtilFn = "") {
    return new Promise((resolve) => {
      if (opts?.mode === "remove") {
        resolve(this.removeElement(opts));
      } else if (opts?.mode === "on") {
        resolve(this.addEvent(opts, iframe, postMsgUtilFn));
      } else {
        resolve(this.appendNode(opts));
      }
    });
  }

  /**
   * 移除节点
   *
   * @param {object} opts - 选项参数
   * @returns {Promise} - Promise 对象
   */
  removeElement(opts) {
    return new Promise((resolve) => {
      const element = document.getElementById(opts.elementId);
      if (element) {
        element.remove();
        resolve({
          data: {
            code: 200,
            message: `移除节点${opts.elementId}成功`,
          },
        });
      } else {
        resolve({
          data: {
            code: -1,
            message: `移除节点${opts.elementId}失败，未找到该节点`,
          },
        });
      }
    });
  }

  /**
   * 添加事件
   *
   * @param {object} opts - 选项参数
   * @param {string} iframe - iframe 元素
   * @param {string} postMsgUtilFn - postMessage 工具函数
   * @returns {Promise} - Promise 对象
   */
  addEvent(opts, iframe, postMsgUtilFn) {
    return new Promise((resolve, reject) => {
      if (opts.elementId) {
        try {
          $(`#${opts.elementId}`).on(opts?.event || "click", function (e) {
            postMsgUtilFn &&
              iframe &&
              opts?.eventListenerName &&
              postMsgUtilFn.trigger(iframe, opts.eventListenerName, {
                data: {
                  code: 200,
                  event: opts?.event || "click",
                  eventData: parse(stringify(e)),
                },
              });
          });
          resolve({
            data: {
              code: 200,
              event: opts?.event || "click",
              eventListenerName: opts?.eventListenerName,
            },
          });
        } catch (error) {
          reject(error);
        }
      } else {
        reject(`ElementId must have value`);
      }
    });
  }

  /**
   * 添加节点
   *
   * @param {object} opts - 选项参数
   * @returns {Promise} - Promise 对象
   */
  appendNode(opts) {
    return new Promise((resolve) => {
      opts?.html ? $("body").append(opts.html) : "";
      resolve({
        data: {
          code: 200,
          message: `节点创建成功`,
        },
      });
    });
  }

  /**
   * 处理队列
   *
   * @param {*} key map标识
   * @param {*} instance 实例
   */
  handleQueueMaps(key, instance = {}) {
    if (key && Object.keys(instance).length) {
      if (this.queueMaps.has(key)) {
        const queues = this.queueMaps.get(key);
        this.queueMaps.set(key, { ...queues, ...instance });
      } else {
        this.queueMaps.set(key, instance);
      }
    } else {
      this.queueMaps.set(key, instance);
    }
  }

  /**
   * 获取地图类实例对象
   * @param {*} key
   * @param {*} className
   * @returns
   */
  getQueueMap(key) {
    return this.queueMaps.get(key);
  }

  /**
   * 对类进行转换
   * @param {*} className
   * @param {*} obj
   * @returns
   */
  convertClassName(className, obj) {
    if (className.indexOf(".") !== -1) {
      const classNames = className.split(".");
      for (let item of classNames) {
        obj = obj[item];
      }
      return obj;
    } else {
      return obj[className];
    }
  }

  /**
   * 格式化 className
   * @param {*} className
   * @returns string
   */
  formatClassName(className) {
    if (className.split("-").length < 2) {
      return `${className}-0`;
    } else {
      const classNames = className.split("-");
      if (/^\d+$/.test(classNames[1])) {
        return className;
      } else {
        return `${classNames[0]}-0`;
      }
    }
  }

  /**
   * 获取真实的实例化对象名称
   * @param {*} className
   * @returns string
   */
  getRealClassName(className) {
    const classNames = className.split("-");
    if (classNames.length < 2) {
      return `${className}`;
    }
    return classNames[0];
  }

  /**
   * 生成UUID
   * @returns string
   */
  uuid() {
    if (typeof CryptoJS === "object") {
      if (typeof CryptoJS.randomUUID === "function") {
        return CryptoJS.randomUUID();
      }
      if (
        typeof CryptoJS.getRandomValues === "function" &&
        typeof Uint8Array === "function"
      ) {
        const callback = (c) => {
          const num = Number(c);
          return (
            num ^
            (CryptoJS.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))
          ).toString(16);
        };
        return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, callback);
      }
    }
    let timestamp = new Date().getTime();
    let perforNow =
      (typeof performance !== "undefined" &&
        performance.now &&
        performance.now() * 1000) ||
      0;

    const crypto = window.crypto || window.msCrypto;
    return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
      let random = (crypto.getRandomValues(new Uint8Array(1))[0] / 256) * 16;
      if (timestamp > 0) {
        random = (timestamp + random) % 16 | 0;
        timestamp = Math.floor(timestamp / 16);
      } else {
        random = (perforNow + random) % 16 | 0;
        perforNow = Math.floor(perforNow / 16);
      }
      return (c === "x" ? random : (random & 0x3) | 0x8).toString(16);
    });
  }

  /**
   * 解析class实例
   * classOptions = {
   *     map: 'xxxx' / false / 不传, // 地图实例
   *     // 单个实例
   *     view: 'xxxx', // 实例id,直接实例id
   *     // 实例下的某个属性
   *     layers {
   *        key: '实例id',
   *     },
   *     // 实例集合
   *     layers ['实例id'],
   * }
   * @returns object
   */
  parseClassObj(classOptions) {
    let classArr = {};
    if (Object.keys(classOptions).length) {
      const { map = false, ...otherClassOptions } = classOptions;
      let currentMap = null;
      if (map) {
        currentMap = this.getQueueMap(map);
        classArr.map = currentMap;
      }
      for (const _key of Object.keys(otherClassOptions)) {
        if (isArray(otherClassOptions[_key])) {
          // 判断是否为数组，如果为数组，则进行循环查询
          classArr[_key] = this.parseArray(_key, otherClassOptions[_key]);
        } else if (isPlainObject(otherClassOptions[_key])) {
          classArr[_key] = this.getQueueMap(
            `${otherClassOptions[_key]["key"]}`
          );
        } else {
          classArr[_key] = this.getQueueMap(`${otherClassOptions[_key]}`);
        }
      }
    }
    return classArr;
  }

  /**
   * 解析数组类型的类选项
   *
   * @param {string} key - 类选项的键名
   * @param {array} value - 类选项的值
   * @returns {array} - 解析后的数组
   */
  parseArray(key, value) {
    const arrList = [];
    for (const __key in value) {
      arrList.push(this.getQueueMap(`${value[__key]}`));
    }
    return arrList;
  }

  /**
   *  oid 转className
   */
  parseOidToClassName(str) {
    if (str) {
      const strList = str.split("-");
      return strList[0];
    }
    return null;
  }

  /**
   * 加密
   * @param {*} content
   * @returns
   */
  encrypt(content) {
    return CryptoJS.AES.encrypt(content, this.key, {
      mode: CryptoJS.mode.ECB,
      padding: CryptoJS.pad.Pkcs7,
    }).toString();
  }
}
